package com.finance.system.nio;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;

public class Server {
    private Selector selector;
    private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);//调整缓存的大小可以看到打印输出的变化

    String str;
    public void start() throws IOException {
        // 打开服务器套接字通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 服务器配置为非阻塞
        ssc.configureBlocking(false);
        // 进行服务的绑定
        ssc.bind(new InetSocketAddress("localhost", 8001));

        // 通过open()方法找到Selector
        selector = Selector.open();
        // 注册到selector，等待连接
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (!Thread.currentThread().isInterrupted()) {
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = keys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                if (!key.isValid()) {
                    continue;
                }
                if (key.isAcceptable()) {
                    accept(key);
                } else if (key.isReadable()) {
                    read(key);
                } else if (key.isWritable()) {
                    write(key);
                }
                keyIterator.remove(); //该事件已经处理，可以丢弃
            }
        }
    }

    private void write(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        if(StringUtils.isNotEmpty(str)){
            sendBuffer.clear();
            sendBuffer.put(str.getBytes());
            sendBuffer.flip();
            channel.write(sendBuffer);
            channel.register(selector, SelectionKey.OP_READ);
        }
    }

    private void read(SelectionKey key) throws IOException {

        SocketChannel socketChannel = (SocketChannel) key.channel();

        //申请一个临时内存区从缓冲区复制100个字节
        SocketChannelBufferedWrapper channelWrapper = SocketChannelWrapperMap.getWrapper(key).wrapper(socketChannel)
                .prepareRead().read().prepareWrite();

        //如果内存区有剩余,进入循环读取
        while(channelWrapper.getReadBuffer().remaining() > 0){
            //如果发现当前有缓存时，说明上个包并没有读完，接下来用到缓冲字节读取剩余的长度的字节
            if(channelWrapper.getCached()){
                //如果缓冲字节长度 ch + 临时内存剩余长度 teh < length,则全部放进缓冲区内
                if(channelWrapper.checkNeedCacheReadBufferRemaining()){
                    channelWrapper.cacheToBodyBuffer();
                    //缓存完，判断下是否已经完整读取消息,如果读取完成，操作后将标志位重置
                    if(channelWrapper.checkIfCacheCompleted()){
                        channelWrapper.printBody();
                        channelWrapper.resetFlagAndCache();
                    }
                }
                //如果缓冲字节长度 ch + 临时内存剩余长度 teh > length,则读取 length-ch 长度的放进缓冲区，同时
                // 缓冲标识复位&缓冲内存复位&bodyLength复位
                else {
                    channelWrapper.cacheLeftLengthByteToBodyBuffer();
                    channelWrapper.printBody();
                    channelWrapper.resetFlagAndCache();
                }

            }else {
                //检查剩余字节加头部缓冲区 足够头部长度
                if(channelWrapper.checkRemainingEnoughToHead()){
                    //从剩余字节中读取一部分，保证头部读完
                    channelWrapper.cacheLeftLengthByteToHeadBuffer();
                    //如果 剩余字节长度 teh < length + ch,则全部放进缓冲区，并置位 缓冲标识
                    if(channelWrapper.checkNeedCacheReadBufferRemaining()){
                        channelWrapper.cacheToBodyBuffer();
                        channelWrapper.setCached(Boolean.TRUE);
                    }
                    //否则本次只缓存部分字节，
                    else{
                        channelWrapper.cacheLeftLengthByteToBodyBuffer();
                    }
                }

            }
        }

    }

    private void accept(SelectionKey key) throws IOException {
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = ssc.accept();
        clientChannel.configureBlocking(false);
        clientChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("a new client connected "+clientChannel.getRemoteAddress());
    }

    public static void main(String[] args) throws IOException {
        System.out.println("server started...");
        new Server().start();
    }
}
